Acknowledgements

Material created by Peter Mac Data Science.

Objectives

  • Demonstrate how to create stripcharts with ggplot2’s geom_jitter()
  • Demonstrate the use of faceting
  • Demonstrate using tidyr’s gather() to convert from wide to long (tidy) format
  • Demonstrate dplyr’s select() to choose columns and filter() to choose rows
  • Demonstrate use of dplyr’s case_when() instead of multiple ifelse()s
  • Demonstrate the powerful pipe %>%

Introduction

In this tutorial, we will learn some R through creating stripcharts to visualise results from an RNA-seq experiment.

Stripcharts can be used to visualise the expression of samples by groups. They are used in RNA-seq analyses to check the top differentially expressed genes (or favourite genes). They enable us to see if the replicates within groups have similar expression values and to compare expression values between groups. In RNA-seq we plot the normalised count values.

RNA-seq dataset

We will again use the published RNA-seq data from the Nature Cell Biology paper by Fu et al. 2015. This study examined expression in basal and luminal cells from mice at different stages (virgin, pregnant and lactating).

Here we will work with the normalised counts for all 12 samples.

First we will load in the libraries we need. These are already installed for you but if you need to install it on your own computer you can use install.packages('tidyverse').

library(tidyverse)
norm_counts <- read_tsv("/training/r-intro-tidyverse/data/limma-voom_normalised_counts.tsv.gz")

Let’s take a look at the norm_counts data.

norm_counts

How many rows and columns are in norm_counts?

Stripcharts of multiple genes

We can make stripcharts to view the expression of multiple genes. Let’s plot the expression of the top 10 differentially expressed genes in luminal cells from the pregnant mice versus the luminal cells from the lactating mice. In the volcano plot tutorial we showed how to get these top 10 gene symbols. Here we will show how to create an object with the symbols manually. Remember, in R we use c() to combine multiple values.

top10_syms <- c("Csn1s2b", "Slc25a1", "Slc34a2", "Atp2b2", "Acacb", "Slc30a2", "Elovl5", "Egf", "Ceacam10", "Pmvk")

Then we extract the normalised counts information for these genes from the norm_counts file.

Filtering rows with filter()

We can use dplyr’s filter() to filter rows. Let’s take a look at how filter() works.

To use filter() we specify the data and the column(s) we want to use to filter with our criteria.

If we wanted to filter for rows (genes) in the MCL1.DG sample that have expression above a threshold (e.g. 5) we would write below.

filter(norm_counts, MCL1.DG > 5)

If we wanted to filter for genes that have an expression value above 5 in both the basal virgin samples (MCL1.DG and MCL1.DH) we specify both columns and the criteria. Note that we need to specify the threshold for each column e.g MCL1.DG > 5 & MCL1.DH > 5 and not MCL1.DG & MCL1.DH > 5.

filter(norm_counts, MCL1.DG > 5 & MCL1.DH > 5)

To filter this dataset for the rows that contain the Csn1s2b gene we would write below. Note that we use a == when we want to test if a value matches exactly.

filter(norm_counts, SYMBOL == "Csn1s2b")

If we wanted to filter for 2 genes we could write that as below. Here we use “|” which means or as we want the row if the SYMBOL column contains Csn1s2b or Slc25a1.

filter(norm_counts, SYMBOL == "Csn1s2b" | SYMBOL == "Slc25a1")

Exercise

  • Try to filter for all rows containing casein in the GENENAME column. Hint: Take a look at the help for str_detect() a function from the tidyverse stringr package.

If we want to filter the rows for our top 10 genes. We use SYMBOL %in% top10_syms which means we want all the rows where the SYMBOL column contains one of the gene symbols in top10_syms. Remember, in R we use %in% to test if a value is in a set of values.

top10_counts <- filter(norm_counts, SYMBOL %in% top10_syms)

Take a look.

top10_counts

Selecting columns with select()

We don’t need the ENTREZID and GENENAME columns, we are only going to use the SYMBOL column and the counts so we can use select() to remove these columns.

filter() is used to choose rows, to choose columns we use select(). Let’s take a look at how select() works.

If we wanted to select the gene symbol column we would write below.

select(top10_counts, SYMBOL)

If we wanted the SYMBOL column and the MCL1.DG sample column we would write below.

select(top10_counts, SYMBOL, MCL1.DG)

We can select column ranges with :.

select(top10_counts, ENTREZID:GENENAME)

There are also useful helper functions you can use inside select(), such as contains(), starts_with() and ends_with().

Exercise

  • Try to select all (and only) the counts columns. Hint: There is more than one way to do it.

We can also use select() to remove columns by specifying a “-”" before the column name(s).

Let’s remove the ENTREZID and GENENAME columns so we only keep the SYMBOL column and the sample expression values.

top10_counts <- select(top10_counts, -ENTREZID, -GENENAME)
top10_counts

The base R way of selecting columns

As mentioned in the tidyverse tutorial here, in base R we can select columns using $ or [], for example, to select the SYMBOL column we could use top10_counts$SYMBOL or top10_counts[, "SYMBOL"]. The $ operator works well for single columns, but for multiple columns it quickly starts to get cumbersome as we need to use the [] operator and c() for combining the required columns. The column names also need quotation marks. For example, to access both the SYMBOL and GENENAME columns we would use top10_counts[, c("SYMBOL", "GENENAME")] whereas with tidyverse’s dplyr it is a lot more intuitive select(top10_counts, SYMBOL, GENENAME).

Converting wide into tidy format with gather()

To more easily plot with ggplot2 we need to change the data into “tidy data”. There are 3 rules of tidy data:

  1. Each variable must have its own column.
  2. Each observation must have its own row.
  3. Each value must have its own cell.

In our expression table there should be just one column for all expression values instead of multiple columns. We can use tidyr’s gather() to easily change the format.

The SYMBOL information is already in a column so we use “-” to say leave that column alone. Then gather() will reformat all the other columns (the expression values) into two columns “key” and “value”. The “key” column will contain the column names (our sample ids), and the “value” column will contain the values from the columns (our expression values). We will give the key column the name “Sample” and the value column the name “Norm_counts”.

# change to tidy data format (all expression values in one column)
top10_counts <- gather(top10_counts, key = Sample, value = Norm_counts, -SYMBOL)

Take a look.

top10_counts

Take a closer look with View()

View(top10_counts)

Exercise

  • Try running gather() on the norm_counts object and save it as an object called testing (i.e. run testing <- gather(norm_counts). What do you think of the output? Can you improve it so that there is a column with sample ids and a column with counts.
  • spread() is the opposite of gather(). Try running spread() on the top10_counts object and see if you can regenerate the table with samples in columns.

Testing multiple conditions with case_when()

We want to plot and compare the expression in the groups, so we use mutate()to add a column called “Group” to say what group each sample belongs to. Here we use dplyr’s case_when() to say if the same id matches are conditions then add the appropriate group name into the Group column. We could use ifelse() as we did in the volcano plot tutorial. However, when there are many conditions to test, as there are here, case_when() is easier to use.

top10_counts <- mutate(top10_counts, Group=case_when(
        Sample %in% c("MCL1.DG", "MCL1.DH")  ~ "basal virgin",
        Sample %in% c("MCL1.DI", "MCL1.DJ")  ~ "basal pregnant",
        Sample %in% c("MCL1.DK", "MCL1.DL")  ~ "basal lactate",
        Sample %in% c("MCL1.LA", "MCL1.LB")  ~ "luminal virgin",
        Sample %in% c("MCL1.LC", "MCL1.LD")  ~ "luminal pregnant",
        Sample %in% c("MCL1.LE", "MCL1.LF")  ~ "luminal lactate"
       ))

Take a look at the data again with View()

View(top10_counts)

Exercise

  • Try to use case_when() to add a column called CellType. This column should contain the value basal if the Group column contains the word basal, or luminal if the Group contains luminal. Save it as an object called testing. Hint: Use str_detect() inside case_when().

Creating stripcharts with geom_jitter()

Now we can make a stripchart. We plot the Group on the X axis and the Norm_counts on the y axis. We will use + geom_jitter() to create a jitter plot. A jitter plot is similar to a scatter plot but adds a small amount of random variation to the location of each point so they don’t overlap. Why do we not just use a scatter plot? Let’s take a look.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts)) +
  geom_point()

Some of the points are overlapping so we use geom_jitter() to avoid having overlapping points.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts)) +
  geom_jitter()

Creating stripcharts for each gene with facet_wrap()

The points are no longer overlapping, however, this is all the genes in one plot. ggplot2 has a really useful feature called faceting that we can use. facet_wrap() will create plots for every value in a column in our data. We would like a stripchart of expression values for each gene so we add + facet_wrap(~SYMBOL) to say we want to a plot for each value in the SYMBOL column. There is also facet_grid() which is most useful when you have two discrete variables, and all combinations of the variables exist in the data.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts)) +
  geom_jitter() +
  facet_wrap(~SYMBOL)

We can change the number of rows (nrows) or columns (ncols) to balance the plot. Let’s try 2 rows (nrow=2).

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2)

We can add col=Group to say we want to colour by the groups (the Group column we added).

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts, col=Group)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2)

We can add + theme(axis.text.x = element_text(angle = 90) to make the x axis labels vertical so they don’t overlap.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts, col=Group)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2) +
  theme(axis.text.x = element_text(angle = 90)) 

Ordering categories along an axis

The groups have been plotted in alphabetical order on the x axis, however, we may want to change the order. We may prefer to plot the groups in order of stage, for example, basal virgin, basal pregnant, basal lactate, luminal virgin, luminal pregnant, luminal lactate. In the volcano plot tutorial we showed how to change the order of items in the legend with breaks= into the scale layer. Let’s try that here.

First let’s make an object with the group order that we want.

group_order <- c("basal virgin", "basal pregnant", "basal lactate", "luminal virgin", "luminal pregnant", "luminal lactate")

Then let’s add this group_order into breaks=.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts, col=Group)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2) +
  theme(axis.text.x = element_text(angle = 90)) +
  scale_colour_discrete(breaks=group_order)

That reordered the legend but notice that it didn’t reorder the groups on the x axis. If we want to reorder groups along an axis with ggplot we need to make the column with the groups into an R data type called a factor. Factors in R are used to specify categories.

We’ll add another column called “Group_f” and make the Group into a factor and specify what order we want the levels (names of the categories).

top10_counts <- mutate(top10_counts, Group_f=factor(Group, levels=group_order))

Take a look with View().

View(top10_counts)

The Group and the Group_f column look the same but take a look by typing top10_counts.

top10_counts

Notice that the Group column has under the heading, it is a character data type, while the Group_f column has under the heading, it is a factor data type. The str() command is useful to check the data types in objects.

str(top10_counts)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   120 obs. of  5 variables:
 $ SYMBOL     : chr  "Slc25a1" "Pmvk" "Egf" "Slc30a2" ...
 $ Sample     : chr  "MCL1.DG" "MCL1.DG" "MCL1.DG" "MCL1.DG" ...
 $ Norm_counts: num  4.881 3.618 -0.89 0.951 -0.229 ...
 $ Group      : chr  "basal virgin" "basal virgin" "basal virgin" "basal virgin" ...
 $ Group_f    : Factor w/ 6 levels "basal virgin",..: 1 1 1 1 1 1 1 1 1 1 ...

str() shows us Group_f column is a Factor with 6 levels. To see the levels we can use levels(). To check the levels of the Group_f columns we could use select() to select the Group_f column followed by pull() to pull the values out of column format.

levels(pull(select(top10_counts, Group_f)))
[1] "basal virgin"     "basal pregnant"   "basal lactate"    "luminal virgin"   "luminal pregnant" "luminal lactate" 

However, in this case it’s simpler to use the base R method, as we can use top10_counts$Group_f to access the values in the Group_f column directly.

levels(top10_counts$Group_f)
[1] "basal virgin"     "basal pregnant"   "basal lactate"    "luminal virgin"   "luminal pregnant" "luminal lactate" 

We can then change our plot to use the “Group_f” column instead of Group (change x= and col=).

ggplot(data=top10_counts, mapping=aes(x=Group_f, y=Norm_counts, col=Group_f)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2) +
  theme(axis.text.x = element_text(angle = 90)) 

Now both the legend and the x axis have the groups in the order that we want.

These are the top genes in the comparison of luminal cells from pregnant vs lactating mice and this type of plot enables us to see what the expression values look like in all the groups. We can see that some genes, such as Pmvk, have more similar expression across all the groups than others, such as Csn1s2b.

Notice that the genes have also been plotted in alphabetical order in the facets. If we wanted to plot these genes in the order of most signficant, then we need to make symbol column into a factor as we did for the groups.

Exercise

  • Plot the genes in order of the significance. Hint: Use mutate to add a column called SYMBOL_f containing SYMBOL as a factor with the levels in the order in top10_syms. Then remke the plot using SYMBOL_f in facet_wrap instead of SYMBOL.

Creating workflows using %>%

One of the most useful and powerful things about the tidyverse is dplyr’s pipe operator %>%. This enables you to chain commmands together into workflows. For example, we could chain together the commands we ran earlier on our top10_counts object. Note that like the + we use to add layers to a ggplot, %>% goes at the end of the line and this ‘pipes’ the output into the next command. If we use the %>% we also don’t need to specify the data inside the individual commands, we only need to specify it at the beginning e.g. instead of filter(norm_counts, SYMBOL %in% top10_syms) we use norm_counts %>% filter(SYMBOL %in% top10_syms). We can also pipe outputs directly into ggplot(). The pipe is very useful but some advice on when not to use the pipe is provided by the tidyverse creator Hadley Wickham in the excellent R for Data Science book.

norm_counts %>%
  filter(SYMBOL %in% top10_syms) %>%                             # filter rows for the top 10 genes
  select(-ENTREZID, -GENENAME) %>%                               # remove ENTREZID and GENENAME columns
  gather(Sample, Norm_counts, -SYMBOL) %>%                       # convert from wide to long (tidy) format
  mutate(Group=case_when(                                        # add a column to specify groups
        Sample %in% c("MCL1.DG", "MCL1.DH")  ~ "basal virgin",
        Sample %in% c("MCL1.DI", "MCL1.DJ")  ~ "basal pregnant",
        Sample %in% c("MCL1.DK", "MCL1.DL")  ~ "basal lactate",
        Sample %in% c("MCL1.LA", "MCL1.LB")  ~ "luminal virgin",
        Sample %in% c("MCL1.LC", "MCL1.LD")  ~ "luminal pregnant",
        Sample %in% c("MCL1.LE", "MCL1.LF")  ~ "luminal lactate"
       )) %>%
  mutate(Group_f=factor(Group, levels=group_order)) %>%           # convert groups column to factor data type to specify ordering
  ggplot(mapping=aes(x=Group_f, y=Norm_counts, col=Group_f)) +    # make stripcharts faceted by gene
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2) +
  theme(axis.text.x = element_text(angle = 90)) 

We only have two points per group here but if there were more points you could overlay an error bar with geom_errorbar() or combine a geom_boxplot() with the geom_jitter(), as shown for the stripcharts in the tutorial here.

Exercise

  • Starting from norm_counts make a workflow using %>% that creates stripcharts for the genes Trp53, Brca1, Brca2.
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIFIiCmF1dGhvcjogIk1hcmlhIERveWxlIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0b2NfZGVwdGg6IDQKc3VidGl0bGU6IHZpc3VhbGlzaW5nIFJOQS1zZXEgZGF0YSB3aXRoIHN0cmlwY2hhcnRzCi0tLQoKIyMjIyBBY2tub3dsZWRnZW1lbnRzCk1hdGVyaWFsIGNyZWF0ZWQgYnkgUGV0ZXIgTWFjIERhdGEgU2NpZW5jZS4KCiMjIE9iamVjdGl2ZXMKICAKKiBEZW1vbnN0cmF0ZSBob3cgdG8gY3JlYXRlIHN0cmlwY2hhcnRzIHdpdGggZ2dwbG90MidzIGBnZW9tX2ppdHRlcigpYAoqIERlbW9uc3RyYXRlIHRoZSB1c2Ugb2YgZmFjZXRpbmcKKiBEZW1vbnN0cmF0ZSB1c2luZyB0aWR5cidzIGBnYXRoZXIoKWAgdG8gY29udmVydCBmcm9tIHdpZGUgdG8gbG9uZyAodGlkeSkgZm9ybWF0CiogRGVtb25zdHJhdGUgZHBseXIncyBgc2VsZWN0KClgIHRvIGNob29zZSBjb2x1bW5zIGFuZCBgZmlsdGVyKClgIHRvIGNob29zZSByb3dzCiogRGVtb25zdHJhdGUgdXNlIG9mIGRwbHlyJ3MgYGNhc2Vfd2hlbigpYCBpbnN0ZWFkIG9mIG11bHRpcGxlIGBpZmVsc2UoKWBzCiogRGVtb25zdHJhdGUgdGhlIHBvd2VyZnVsIHBpcGUgYCU+JWAKCgojIyBJbnRyb2R1Y3Rpb24KCkluIHRoaXMgdHV0b3JpYWwsIHdlIHdpbGwgbGVhcm4gc29tZSBSIHRocm91Z2ggY3JlYXRpbmcgc3RyaXBjaGFydHMgdG8gdmlzdWFsaXNlIHJlc3VsdHMgZnJvbSBhbiBSTkEtc2VxIGV4cGVyaW1lbnQuCgpTdHJpcGNoYXJ0cyBjYW4gYmUgdXNlZCB0byB2aXN1YWxpc2UgdGhlIGV4cHJlc3Npb24gb2Ygc2FtcGxlcyBieSBncm91cHMuIFRoZXkgYXJlIHVzZWQgaW4gUk5BLXNlcSBhbmFseXNlcyB0byBjaGVjayB0aGUgdG9wIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyAob3IgZmF2b3VyaXRlIGdlbmVzKS4gVGhleSBlbmFibGUgdXMgdG8gc2VlIGlmIHRoZSByZXBsaWNhdGVzIHdpdGhpbiBncm91cHMgaGF2ZSBzaW1pbGFyIGV4cHJlc3Npb24gdmFsdWVzIGFuZCB0byBjb21wYXJlIGV4cHJlc3Npb24gdmFsdWVzIGJldHdlZW4gZ3JvdXBzLiBJbiBSTkEtc2VxIHdlIHBsb3QgdGhlIG5vcm1hbGlzZWQgY291bnQgdmFsdWVzLgoKIyMjIFJOQS1zZXEgZGF0YXNldAoKV2Ugd2lsbCBhZ2FpbiB1c2UgdGhlIHB1Ymxpc2hlZCBSTkEtc2VxIGRhdGEgZnJvbSB0aGUgTmF0dXJlIENlbGwgQmlvbG9neSBwYXBlciBieSBbRnUgZXQgYWwuIDIwMTVdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzI1NzMwNDcyKS4gVGhpcyBzdHVkeSBleGFtaW5lZCBleHByZXNzaW9uIGluIGJhc2FsIGFuZCBsdW1pbmFsIGNlbGxzIGZyb20gbWljZSBhdCBkaWZmZXJlbnQgc3RhZ2VzICh2aXJnaW4sIHByZWduYW50IGFuZCBsYWN0YXRpbmcpLgoKSGVyZSB3ZSB3aWxsIHdvcmsgd2l0aCB0aGUgbm9ybWFsaXNlZCBjb3VudHMgZm9yIGFsbCAxMiBzYW1wbGVzLgohW10oaW1hZ2VzL21vdXNlX2V4cC5wbmcpCgpGaXJzdCB3ZSB3aWxsIGxvYWQgaW4gdGhlIGxpYnJhcmllcyB3ZSBuZWVkLiBUaGVzZSBhcmUgYWxyZWFkeSBpbnN0YWxsZWQgZm9yIHlvdSBidXQgaWYgeW91IG5lZWQgdG8gaW5zdGFsbCBpdCBvbiB5b3VyIG93biBjb21wdXRlciB5b3UgY2FuIHVzZSBgaW5zdGFsbC5wYWNrYWdlcygndGlkeXZlcnNlJylgLiAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpgYGB7ciByZXN1bHRzID0gJ2hpZGUnfQpub3JtX2NvdW50cyA8LSByZWFkX3RzdigiL3RyYWluaW5nL3ItaW50cm8tdGlkeXZlcnNlL2RhdGEvbGltbWEtdm9vbV9ub3JtYWxpc2VkX2NvdW50cy50c3YuZ3oiKQpgYGAKCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBgbm9ybV9jb3VudHNgIGRhdGEuCgpgYGB7cn0Kbm9ybV9jb3VudHMKYGBgCgpIb3cgbWFueSByb3dzIGFuZCBjb2x1bW5zIGFyZSBpbiBgbm9ybV9jb3VudHNgPwoKIyMgU3RyaXBjaGFydHMgb2YgbXVsdGlwbGUgZ2VuZXMKCldlIGNhbiBtYWtlIHN0cmlwY2hhcnRzIHRvIHZpZXcgdGhlIGV4cHJlc3Npb24gb2YgbXVsdGlwbGUgZ2VuZXMuIExldCdzIHBsb3QgdGhlIGV4cHJlc3Npb24gb2YgdGhlIHRvcCAxMCBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMgaW4gbHVtaW5hbCBjZWxscyBmcm9tIHRoZSBwcmVnbmFudCBtaWNlIHZlcnN1cyB0aGUgbHVtaW5hbCBjZWxscyBmcm9tIHRoZSBsYWN0YXRpbmcgbWljZS4gSW4gdGhlIHZvbGNhbm8gcGxvdCB0dXRvcmlhbCB3ZSBzaG93ZWQgaG93IHRvIGdldCB0aGVzZSB0b3AgMTAgZ2VuZSBzeW1ib2xzLiBIZXJlIHdlIHdpbGwgc2hvdyBob3cgdG8gY3JlYXRlIGFuIG9iamVjdCB3aXRoIHRoZSBzeW1ib2xzIG1hbnVhbGx5LiBSZW1lbWJlciwgaW4gUiB3ZSB1c2UgYGMoKWAgdG8gY29tYmluZSBtdWx0aXBsZSB2YWx1ZXMuCgpgYGB7cn0KdG9wMTBfc3ltcyA8LSBjKCJDc24xczJiIiwgIlNsYzI1YTEiLCAiU2xjMzRhMiIsICJBdHAyYjIiLCAiQWNhY2IiLCAiU2xjMzBhMiIsICJFbG92bDUiLCAiRWdmIiwgIkNlYWNhbTEwIiwgIlBtdmsiKQpgYGAKClRoZW4gd2UgZXh0cmFjdCB0aGUgbm9ybWFsaXNlZCBjb3VudHMgaW5mb3JtYXRpb24gZm9yIHRoZXNlIGdlbmVzIGZyb20gdGhlIGBub3JtX2NvdW50c2AgZmlsZS4KCiMjIEZpbHRlcmluZyByb3dzIHdpdGggYGZpbHRlcigpYAoKV2UgY2FuIHVzZSBkcGx5cidzIGBmaWx0ZXIoKWAgdG8gZmlsdGVyIHJvd3MuIExldCdzIHRha2UgYSBsb29rIGF0IGhvdyBgZmlsdGVyKClgIHdvcmtzLiAKClRvIHVzZSBgZmlsdGVyKClgIHdlIHNwZWNpZnkgdGhlIGRhdGEgYW5kIHRoZSBjb2x1bW4ocykgd2Ugd2FudCB0byB1c2UgdG8gZmlsdGVyIHdpdGggb3VyIGNyaXRlcmlhLiAKCklmIHdlIHdhbnRlZCB0byBmaWx0ZXIgZm9yIHJvd3MgKGdlbmVzKSBpbiB0aGUgTUNMMS5ERyBzYW1wbGUgdGhhdCBoYXZlIGV4cHJlc3Npb24gYWJvdmUgYSB0aHJlc2hvbGQgKGUuZy4gNSkgd2Ugd291bGQgd3JpdGUgYmVsb3cuCmBgYHtyfQpmaWx0ZXIobm9ybV9jb3VudHMsIE1DTDEuREcgPiA1KQpgYGAKCklmIHdlIHdhbnRlZCB0byBmaWx0ZXIgZm9yIGdlbmVzIHRoYXQgaGF2ZSBhbiBleHByZXNzaW9uIHZhbHVlIGFib3ZlIDUgaW4gYm90aCB0aGUgYmFzYWwgdmlyZ2luIHNhbXBsZXMgKE1DTDEuREcgYW5kIE1DTDEuREgpIHdlIHNwZWNpZnkgYm90aCBjb2x1bW5zIGFuZCB0aGUgY3JpdGVyaWEuIE5vdGUgdGhhdCB3ZSBuZWVkIHRvIHNwZWNpZnkgdGhlIHRocmVzaG9sZCBmb3IgZWFjaCBjb2x1bW4gZS5nIGBNQ0wxLkRHID4gNSAmIE1DTDEuREggPiA1YCBhbmQgbm90IGBNQ0wxLkRHICYgTUNMMS5ESCA+IDVgLgpgYGB7cn0KZmlsdGVyKG5vcm1fY291bnRzLCBNQ0wxLkRHID4gNSAmIE1DTDEuREggPiA1KQpgYGAKCgpUbyBmaWx0ZXIgdGhpcyBkYXRhc2V0IGZvciB0aGUgcm93cyB0aGF0IGNvbnRhaW4gdGhlIENzbjFzMmIgZ2VuZSB3ZSB3b3VsZCB3cml0ZSBiZWxvdy4gTm90ZSB0aGF0IHdlIHVzZSBhIGA9PWAgd2hlbiB3ZSB3YW50IHRvIHRlc3QgaWYgYSB2YWx1ZSBtYXRjaGVzIGV4YWN0bHkuCmBgYHtyfQpmaWx0ZXIobm9ybV9jb3VudHMsIFNZTUJPTCA9PSAiQ3NuMXMyYiIpCmBgYApJZiB3ZSB3YW50ZWQgdG8gZmlsdGVyIGZvciAyIGdlbmVzIHdlIGNvdWxkIHdyaXRlIHRoYXQgYXMgYmVsb3cuIEhlcmUgd2UgdXNlICJ8IiB3aGljaCBtZWFucyBvciBhcyB3ZSB3YW50IHRoZSByb3cgaWYgdGhlIFNZTUJPTCBjb2x1bW4gY29udGFpbnMgQ3NuMXMyYiBvciBTbGMyNWExLgoKYGBge3J9CmZpbHRlcihub3JtX2NvdW50cywgU1lNQk9MID09ICJDc24xczJiIiB8IFNZTUJPTCA9PSAiU2xjMjVhMSIpCmBgYAoKIyMjIyBFeGVyY2lzZQoKKiBUcnkgdG8gZmlsdGVyIGZvciBhbGwgcm93cyBjb250YWluaW5nIGNhc2VpbiBpbiB0aGUgR0VORU5BTUUgY29sdW1uLiBIaW50OiBUYWtlIGEgbG9vayBhdCB0aGUgaGVscCBmb3IgYHN0cl9kZXRlY3QoKWAgYSBmdW5jdGlvbiBmcm9tIHRoZSB0aWR5dmVyc2UgKipzdHJpbmdyKiogcGFja2FnZS4KCklmIHdlIHdhbnQgdG8gZmlsdGVyIHRoZSByb3dzIGZvciBvdXIgdG9wIDEwIGdlbmVzLiBXZSB1c2UgYFNZTUJPTCAlaW4lIHRvcDEwX3N5bXNgIHdoaWNoIG1lYW5zIHdlIHdhbnQgYWxsIHRoZSByb3dzIHdoZXJlIHRoZSBTWU1CT0wgY29sdW1uIGNvbnRhaW5zIG9uZSBvZiB0aGUgZ2VuZSBzeW1ib2xzIGluIGB0b3AxMF9zeW1zYC4gUmVtZW1iZXIsIGluIFIgd2UgdXNlIGAlaW4lYCB0byB0ZXN0IGlmIGEgdmFsdWUgaXMgaW4gYSBzZXQgb2YgdmFsdWVzLgoKYGBge3J9CnRvcDEwX2NvdW50cyA8LSBmaWx0ZXIobm9ybV9jb3VudHMsIFNZTUJPTCAlaW4lIHRvcDEwX3N5bXMpCmBgYAoKVGFrZSBhIGxvb2suCgpgYGB7cn0KdG9wMTBfY291bnRzCmBgYAoKCiMjIFNlbGVjdGluZyBjb2x1bW5zIHdpdGggYHNlbGVjdCgpYAoKV2UgZG9uJ3QgbmVlZCB0aGUgRU5UUkVaSUQgYW5kIEdFTkVOQU1FIGNvbHVtbnMsIHdlIGFyZSBvbmx5IGdvaW5nIHRvIHVzZSB0aGUgU1lNQk9MIGNvbHVtbiBhbmQgdGhlIGNvdW50cyBzbyB3ZSBjYW4gdXNlIGBzZWxlY3QoKWAgdG8gcmVtb3ZlIHRoZXNlIGNvbHVtbnMuCgpgZmlsdGVyKClgIGlzIHVzZWQgdG8gY2hvb3NlIHJvd3MsIHRvIGNob29zZSBjb2x1bW5zIHdlIHVzZSBgc2VsZWN0KClgLiBMZXQncyB0YWtlIGEgbG9vayBhdCBob3cgYHNlbGVjdCgpYCB3b3Jrcy4KCklmIHdlIHdhbnRlZCB0byBzZWxlY3QgdGhlIGdlbmUgc3ltYm9sIGNvbHVtbiB3ZSB3b3VsZCB3cml0ZSBiZWxvdy4KCmBgYHtyfQpzZWxlY3QodG9wMTBfY291bnRzLCBTWU1CT0wpCmBgYAoKSWYgd2Ugd2FudGVkIHRoZSBTWU1CT0wgY29sdW1uIGFuZCB0aGUgTUNMMS5ERyBzYW1wbGUgY29sdW1uIHdlIHdvdWxkIHdyaXRlIGJlbG93LgoKYGBge3J9CnNlbGVjdCh0b3AxMF9jb3VudHMsIFNZTUJPTCwgTUNMMS5ERykKYGBgCgpXZSBjYW4gc2VsZWN0IGNvbHVtbiByYW5nZXMgd2l0aCBgOmAuCgpgYGB7cn0Kc2VsZWN0KHRvcDEwX2NvdW50cywgRU5UUkVaSUQ6R0VORU5BTUUpCmBgYAoKVGhlcmUgYXJlIGFsc28gdXNlZnVsIGhlbHBlciBmdW5jdGlvbnMgeW91IGNhbiB1c2UgaW5zaWRlIGBzZWxlY3QoKWAsIHN1Y2ggYXMgYGNvbnRhaW5zKClgLCBgc3RhcnRzX3dpdGgoKWAgYW5kIGBlbmRzX3dpdGgoKWAuCgojIyMjIEV4ZXJjaXNlCgoqIFRyeSB0byBzZWxlY3QgYWxsIChhbmQgb25seSkgdGhlIGNvdW50cyBjb2x1bW5zLiBIaW50OiBUaGVyZSBpcyBtb3JlIHRoYW4gb25lIHdheSB0byBkbyBpdC4KCldlIGNhbiBhbHNvIHVzZSBgc2VsZWN0KClgIHRvIHJlbW92ZSBjb2x1bW5zIGJ5IHNwZWNpZnlpbmcgYSAiLSIiIGJlZm9yZSB0aGUgY29sdW1uIG5hbWUocykuCgpMZXQncyByZW1vdmUgdGhlIEVOVFJFWklEIGFuZCBHRU5FTkFNRSBjb2x1bW5zIHNvIHdlIG9ubHkga2VlcCB0aGUgU1lNQk9MIGNvbHVtbiBhbmQgdGhlIHNhbXBsZSBleHByZXNzaW9uIHZhbHVlcy4KCmBgYHtyfQp0b3AxMF9jb3VudHMgPC0gc2VsZWN0KHRvcDEwX2NvdW50cywgLUVOVFJFWklELCAtR0VORU5BTUUpCnRvcDEwX2NvdW50cwpgYGAKCj4gVGhlIGJhc2UgUiB3YXkgb2Ygc2VsZWN0aW5nIGNvbHVtbnMgCj4gCj4gQXMgbWVudGlvbmVkIGluIHRoZSB0aWR5dmVyc2UgdHV0b3JpYWwgCj4gW2hlcmVdKGh0dHBzOi8vcmF3Z2l0LmNvbS9iaW9pbmZvcm1hdGljcy1jb3JlLXNoYXJlZC10cmFpbmluZy9yLWludGVybWVkaWF0ZS9tYXN0ZXIvMi5kcGx5ci1pbnRyby1saXZlLWNvZGluZy1zY3JpcHQuaHRtbCksIAo+IGluIGJhc2UgUiB3ZSBjYW4gc2VsZWN0IGNvbHVtbnMgdXNpbmcgYCRgIG9yIGBbXWAsIGZvciBleGFtcGxlLCB0byBzZWxlY3QgdGhlIFNZTUJPTCBjb2x1bW4gd2UgY291bGQgdXNlIAo+IGB0b3AxMF9jb3VudHMkU1lNQk9MYCBvciBgdG9wMTBfY291bnRzWywgIlNZTUJPTCJdYC4gVGhlIGAkYCBvcGVyYXRvciB3b3JrcyB3ZWxsIGZvciBzaW5nbGUgY29sdW1ucywgYnV0IGZvciBtdWx0aXBsZSBjb2x1bW5zCj4gaXQgcXVpY2tseSBzdGFydHMgdG8gZ2V0IGN1bWJlcnNvbWUgYXMgd2UgbmVlZCB0byB1c2UgdGhlIGBbXWAgb3BlcmF0b3IgYW5kIGBjKClgIGZvciBjb21iaW5pbmcgdGhlIHJlcXVpcmVkIGNvbHVtbnMuIFRoZSBjb2x1bW4gCj4gbmFtZXMgYWxzbyBuZWVkIHF1b3RhdGlvbiBtYXJrcy4gRm9yIGV4YW1wbGUsIHRvIGFjY2VzcyBib3RoIHRoZSBTWU1CT0wgYW5kIEdFTkVOQU1FIAo+IGNvbHVtbnMgd2Ugd291bGQgdXNlIGB0b3AxMF9jb3VudHNbLCBjKCJTWU1CT0wiLCAiR0VORU5BTUUiKV1gIHdoZXJlYXMgd2l0aCB0aWR5dmVyc2UncyBkcGx5ciBpdCBpcyBhIGxvdCBtb3JlIGludHVpdGl2ZSAKPiBgc2VsZWN0KHRvcDEwX2NvdW50cywgU1lNQk9MLCBHRU5FTkFNRSlgLgoKCiMjIENvbnZlcnRpbmcgd2lkZSBpbnRvIHRpZHkgZm9ybWF0IHdpdGggYGdhdGhlcigpYAoKVG8gbW9yZSBlYXNpbHkgcGxvdCB3aXRoIGdncGxvdDIgd2UgbmVlZCB0byBjaGFuZ2UgdGhlIGRhdGEgaW50byBbInRpZHkgZGF0YSJdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RpZHlfZGF0YSkuIFRoZXJlIGFyZSAzIHJ1bGVzIG9mIHRpZHkgZGF0YToKCiAgMS4gRWFjaCB2YXJpYWJsZSBtdXN0IGhhdmUgaXRzIG93biBjb2x1bW4uCiAgMi4gRWFjaCBvYnNlcnZhdGlvbiBtdXN0IGhhdmUgaXRzIG93biByb3cuCiAgMy4gRWFjaCB2YWx1ZSBtdXN0IGhhdmUgaXRzIG93biBjZWxsLiAKICAKSW4gb3VyIGV4cHJlc3Npb24gdGFibGUgdGhlcmUgc2hvdWxkIGJlIGp1c3Qgb25lIGNvbHVtbiBmb3IgYWxsIGV4cHJlc3Npb24gdmFsdWVzIGluc3RlYWQgb2YgbXVsdGlwbGUgY29sdW1ucy4gV2UgY2FuIHVzZSB0aWR5cidzIGBnYXRoZXIoKWAgdG8gZWFzaWx5IGNoYW5nZSB0aGUgZm9ybWF0LgoKVGhlIFNZTUJPTCBpbmZvcm1hdGlvbiBpcyBhbHJlYWR5IGluIGEgY29sdW1uIHNvIHdlIHVzZSAiLSIgdG8gc2F5IGxlYXZlIHRoYXQgY29sdW1uIGFsb25lLiBUaGVuIGBnYXRoZXIoKWAgd2lsbCByZWZvcm1hdCAqYWxsIHRoZSBvdGhlciBjb2x1bW5zKiAodGhlIGV4cHJlc3Npb24gdmFsdWVzKSBpbnRvIHR3byBjb2x1bW5zICJrZXkiIGFuZCAidmFsdWUiLiBUaGUgImtleSIgY29sdW1uIHdpbGwgY29udGFpbiB0aGUgY29sdW1uIG5hbWVzIChvdXIgc2FtcGxlIGlkcyksIGFuZCB0aGUgInZhbHVlIiBjb2x1bW4gd2lsbCBjb250YWluIHRoZSB2YWx1ZXMgZnJvbSB0aGUgY29sdW1ucyAob3VyIGV4cHJlc3Npb24gdmFsdWVzKS4gV2Ugd2lsbCBnaXZlIHRoZSBrZXkgY29sdW1uIHRoZSBuYW1lICJTYW1wbGUiIGFuZCB0aGUgdmFsdWUgY29sdW1uIHRoZSBuYW1lICJOb3JtX2NvdW50cyIuCgpgYGB7cn0KIyBjaGFuZ2UgdG8gdGlkeSBkYXRhIGZvcm1hdCAoYWxsIGV4cHJlc3Npb24gdmFsdWVzIGluIG9uZSBjb2x1bW4pCnRvcDEwX2NvdW50cyA8LSBnYXRoZXIodG9wMTBfY291bnRzLCBrZXkgPSBTYW1wbGUsIHZhbHVlID0gTm9ybV9jb3VudHMsIC1TWU1CT0wpCmBgYAoKVGFrZSBhIGxvb2suCmBgYHtyfQp0b3AxMF9jb3VudHMKYGBgCgpUYWtlIGEgY2xvc2VyIGxvb2sgd2l0aCBgVmlldygpYApgYGB7ciBldmFsPUZBTFNFfQpWaWV3KHRvcDEwX2NvdW50cykKYGBgCgojIyMjIEV4ZXJjaXNlCgogKiBUcnkgcnVubmluZyBgZ2F0aGVyKClgIG9uIHRoZSBgbm9ybV9jb3VudHNgIG9iamVjdCBhbmQgc2F2ZSBpdCBhcyBhbiBvYmplY3QgY2FsbGVkIGB0ZXN0aW5nYCAoaS5lLiBydW4gYHRlc3RpbmcgPC0gZ2F0aGVyKG5vcm1fY291bnRzKWAuIFdoYXQgZG8geW91IHRoaW5rIG9mIHRoZSBvdXRwdXQ/IENhbiB5b3UgaW1wcm92ZSBpdCBzbyB0aGF0IHRoZXJlIGlzIGEgY29sdW1uIHdpdGggc2FtcGxlIGlkcyBhbmQgYSBjb2x1bW4gd2l0aCBjb3VudHMuCiAqIGBzcHJlYWQoKWAgaXMgdGhlIG9wcG9zaXRlIG9mIGBnYXRoZXIoKWAuIFRyeSBydW5uaW5nIGBzcHJlYWQoKWAgb24gdGhlIGB0b3AxMF9jb3VudHNgIG9iamVjdCBhbmQgc2VlIGlmIHlvdSBjYW4gcmVnZW5lcmF0ZSB0aGUgdGFibGUgd2l0aCBzYW1wbGVzIGluIGNvbHVtbnMuCgojIyBUZXN0aW5nIG11bHRpcGxlIGNvbmRpdGlvbnMgd2l0aCBgY2FzZV93aGVuKClgCgpXZSB3YW50IHRvIHBsb3QgYW5kIGNvbXBhcmUgdGhlIGV4cHJlc3Npb24gaW4gdGhlIGdyb3Vwcywgc28gd2UgdXNlIGBtdXRhdGUoKWB0byBhZGQgYSBjb2x1bW4gY2FsbGVkICJHcm91cCIgdG8gc2F5IHdoYXQgZ3JvdXAgZWFjaCBzYW1wbGUgYmVsb25ncyB0by4gIEhlcmUgd2UgdXNlIGRwbHlyJ3MgYGNhc2Vfd2hlbigpYCB0byBzYXkgaWYgdGhlIHNhbWUgaWQgbWF0Y2hlcyBhcmUgY29uZGl0aW9ucyB0aGVuIGFkZCB0aGUgYXBwcm9wcmlhdGUgZ3JvdXAgbmFtZSBpbnRvIHRoZSBHcm91cCBjb2x1bW4uIFdlIGNvdWxkIHVzZSBgaWZlbHNlKClgIGFzIHdlIGRpZCBpbiB0aGUgdm9sY2FubyBwbG90IHR1dG9yaWFsLiBIb3dldmVyLCB3aGVuIHRoZXJlIGFyZSBtYW55IGNvbmRpdGlvbnMgdG8gdGVzdCwgYXMgdGhlcmUgYXJlIGhlcmUsIGBjYXNlX3doZW4oKWAgaXMgZWFzaWVyIHRvIHVzZS4KCmBgYHtyfQp0b3AxMF9jb3VudHMgPC0gbXV0YXRlKHRvcDEwX2NvdW50cywgR3JvdXA9Y2FzZV93aGVuKAogICAgICAgIFNhbXBsZSAlaW4lIGMoIk1DTDEuREciLCAiTUNMMS5ESCIpICB+ICJiYXNhbCB2aXJnaW4iLAogICAgICAgIFNhbXBsZSAlaW4lIGMoIk1DTDEuREkiLCAiTUNMMS5ESiIpICB+ICJiYXNhbCBwcmVnbmFudCIsCiAgICAgICAgU2FtcGxlICVpbiUgYygiTUNMMS5ESyIsICJNQ0wxLkRMIikgIH4gImJhc2FsIGxhY3RhdGUiLAogICAgICAgIFNhbXBsZSAlaW4lIGMoIk1DTDEuTEEiLCAiTUNMMS5MQiIpICB+ICJsdW1pbmFsIHZpcmdpbiIsCiAgICAgICAgU2FtcGxlICVpbiUgYygiTUNMMS5MQyIsICJNQ0wxLkxEIikgIH4gImx1bWluYWwgcHJlZ25hbnQiLAogICAgICAgIFNhbXBsZSAlaW4lIGMoIk1DTDEuTEUiLCAiTUNMMS5MRiIpICB+ICJsdW1pbmFsIGxhY3RhdGUiCiAgICAgICApKQpgYGAKClRha2UgYSBsb29rIGF0IHRoZSBkYXRhIGFnYWluIHdpdGggYFZpZXcoKWAKYGBge3J9ClZpZXcodG9wMTBfY291bnRzKQpgYGAKCgojIyMjIEV4ZXJjaXNlCgoqIFRyeSB0byB1c2UgYGNhc2Vfd2hlbigpYCB0byBhZGQgYSBjb2x1bW4gY2FsbGVkIENlbGxUeXBlLiBUaGlzIGNvbHVtbiBzaG91bGQgY29udGFpbiB0aGUgdmFsdWUgYmFzYWwgaWYgdGhlIEdyb3VwIGNvbHVtbiBjb250YWlucyB0aGUgd29yZCBiYXNhbCwgb3IgbHVtaW5hbCBpZiB0aGUgR3JvdXAgY29udGFpbnMgbHVtaW5hbC4gU2F2ZSBpdCBhcyBhbiBvYmplY3QgY2FsbGVkIGB0ZXN0aW5nYC4gSGludDogVXNlIGBzdHJfZGV0ZWN0KClgIGluc2lkZSBgY2FzZV93aGVuKClgLgoKCiMjIENyZWF0aW5nIHN0cmlwY2hhcnRzIHdpdGggYGdlb21faml0dGVyKClgCgpOb3cgd2UgY2FuIG1ha2UgYSBzdHJpcGNoYXJ0LiBXZSBwbG90IHRoZSBHcm91cCBvbiB0aGUgWCBheGlzIGFuZCB0aGUgTm9ybV9jb3VudHMgb24gdGhlIHkgYXhpcy4gV2Ugd2lsbCB1c2UgYCsgZ2VvbV9qaXR0ZXIoKWAgdG8gY3JlYXRlIGEgaml0dGVyIHBsb3QuIEEgaml0dGVyIHBsb3QgaXMgc2ltaWxhciB0byBhIHNjYXR0ZXIgcGxvdCBidXQgYWRkcyBhIHNtYWxsIGFtb3VudCBvZiByYW5kb20gdmFyaWF0aW9uIHRvIHRoZSBsb2NhdGlvbiBvZiBlYWNoIHBvaW50IHNvIHRoZXkgZG9uJ3Qgb3ZlcmxhcC4gV2h5IGRvIHdlIG5vdCBqdXN0IHVzZSBhIHNjYXR0ZXIgcGxvdD8gTGV0J3MgdGFrZSBhIGxvb2suIAoKYGBge3J9CmdncGxvdChkYXRhPXRvcDEwX2NvdW50cywgbWFwcGluZz1hZXMoeD1Hcm91cCwgeT1Ob3JtX2NvdW50cykpICsKICBnZW9tX3BvaW50KCkKYGBgCgpTb21lIG9mIHRoZSBwb2ludHMgYXJlIG92ZXJsYXBwaW5nIHNvIHdlIHVzZSBgZ2VvbV9qaXR0ZXIoKWAgdG8gYXZvaWQgaGF2aW5nIG92ZXJsYXBwaW5nIHBvaW50cy4KCmBgYHtyfQpnZ3Bsb3QoZGF0YT10b3AxMF9jb3VudHMsIG1hcHBpbmc9YWVzKHg9R3JvdXAsIHk9Tm9ybV9jb3VudHMpKSArCiAgZ2VvbV9qaXR0ZXIoKQpgYGAKCiMjIENyZWF0aW5nIHN0cmlwY2hhcnRzIGZvciBlYWNoIGdlbmUgd2l0aCBgZmFjZXRfd3JhcCgpYAoKVGhlIHBvaW50cyBhcmUgbm8gbG9uZ2VyIG92ZXJsYXBwaW5nLCBob3dldmVyLCB0aGlzIGlzIGFsbCB0aGUgZ2VuZXMgaW4gb25lIHBsb3QuIGdncGxvdDIgaGFzIGEgcmVhbGx5IHVzZWZ1bCBmZWF0dXJlIGNhbGxlZCBmYWNldGluZyB0aGF0IHdlIGNhbiB1c2UuIGBmYWNldF93cmFwKClgIHdpbGwgY3JlYXRlIHBsb3RzIGZvciBldmVyeSB2YWx1ZSBpbiBhIGNvbHVtbiBpbiBvdXIgZGF0YS4gV2Ugd291bGQgbGlrZSBhIHN0cmlwY2hhcnQgb2YgZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIGVhY2ggZ2VuZSBzbyB3ZSBhZGQgYCArIGZhY2V0X3dyYXAoflNZTUJPTClgIHRvIHNheSB3ZSB3YW50IHRvIGEgcGxvdCBmb3IgZWFjaCB2YWx1ZSBpbiB0aGUgU1lNQk9MIGNvbHVtbi4gVGhlcmUgaXMgYWxzbyBgZmFjZXRfZ3JpZCgpYCB3aGljaCBpcyBtb3N0IHVzZWZ1bCB3aGVuIHlvdSBoYXZlIHR3byBkaXNjcmV0ZSB2YXJpYWJsZXMsIGFuZCBhbGwgY29tYmluYXRpb25zIG9mIHRoZSB2YXJpYWJsZXMgZXhpc3QgaW4gdGhlIGRhdGEuCgpgYGB7cn0KZ2dwbG90KGRhdGE9dG9wMTBfY291bnRzLCBtYXBwaW5nPWFlcyh4PUdyb3VwLCB5PU5vcm1fY291bnRzKSkgKwogIGdlb21faml0dGVyKCkgKwogIGZhY2V0X3dyYXAoflNZTUJPTCkKYGBgCgpXZSBjYW4gY2hhbmdlIHRoZSBudW1iZXIgb2Ygcm93cyAoYG5yb3dzYCkgb3IgY29sdW1ucyAoYG5jb2xzYCkgdG8gYmFsYW5jZSB0aGUgcGxvdC4gTGV0J3MgdHJ5IDIgcm93cyAoYG5yb3c9MmApLgoKYGBge3J9CmdncGxvdChkYXRhPXRvcDEwX2NvdW50cywgbWFwcGluZz1hZXMoeD1Hcm91cCwgeT1Ob3JtX2NvdW50cykpICsKICBnZW9tX2ppdHRlcigpICsKICBmYWNldF93cmFwKH5TWU1CT0wsIG5yb3c9MikKYGBgCgoKV2UgY2FuIGFkZCBgY29sPUdyb3VwYCB0byBzYXkgd2Ugd2FudCB0byBjb2xvdXIgYnkgdGhlIGdyb3VwcyAodGhlIEdyb3VwIGNvbHVtbiB3ZSBhZGRlZCkuCgpgYGB7cn0KZ2dwbG90KGRhdGE9dG9wMTBfY291bnRzLCBtYXBwaW5nPWFlcyh4PUdyb3VwLCB5PU5vcm1fY291bnRzLCBjb2w9R3JvdXApKSArCiAgZ2VvbV9qaXR0ZXIoKSArCiAgZmFjZXRfd3JhcCh+U1lNQk9MLCBucm93PTIpCmBgYAoKV2UgY2FuIGFkZCBgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKWAgdG8gbWFrZSB0aGUgeCBheGlzIGxhYmVscyB2ZXJ0aWNhbCBzbyB0aGV5IGRvbid0IG92ZXJsYXAuCgpgYGB7cn0KZ2dwbG90KGRhdGE9dG9wMTBfY291bnRzLCBtYXBwaW5nPWFlcyh4PUdyb3VwLCB5PU5vcm1fY291bnRzLCBjb2w9R3JvdXApKSArCiAgZ2VvbV9qaXR0ZXIoKSArCiAgZmFjZXRfd3JhcCh+U1lNQk9MLCBucm93PTIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgCmBgYAoKIyMgT3JkZXJpbmcgY2F0ZWdvcmllcyBhbG9uZyBhbiBheGlzCgpUaGUgZ3JvdXBzIGhhdmUgYmVlbiBwbG90dGVkIGluIGFscGhhYmV0aWNhbCBvcmRlciBvbiB0aGUgeCBheGlzLCBob3dldmVyLCB3ZSBtYXkgd2FudCB0byBjaGFuZ2UgdGhlIG9yZGVyLiBXZSBtYXkgcHJlZmVyIHRvIHBsb3QgdGhlIGdyb3VwcyBpbiBvcmRlciBvZiBzdGFnZSwgZm9yIGV4YW1wbGUsIGJhc2FsIHZpcmdpbiwgYmFzYWwgcHJlZ25hbnQsIGJhc2FsIGxhY3RhdGUsIGx1bWluYWwgdmlyZ2luLCBsdW1pbmFsIHByZWduYW50LCBsdW1pbmFsIGxhY3RhdGUuIEluIHRoZSB2b2xjYW5vIHBsb3QgdHV0b3JpYWwgd2Ugc2hvd2VkIGhvdyB0byBjaGFuZ2UgdGhlIG9yZGVyIG9mIGl0ZW1zIGluIHRoZSBsZWdlbmQgd2l0aCBgYnJlYWtzPWAgaW50byB0aGUgc2NhbGUgbGF5ZXIuIExldCdzIHRyeSB0aGF0IGhlcmUuCgpGaXJzdCBsZXQncyBtYWtlIGFuIG9iamVjdCB3aXRoIHRoZSBncm91cCBvcmRlciB0aGF0IHdlIHdhbnQuCmBgYHtyfQpncm91cF9vcmRlciA8LSBjKCJiYXNhbCB2aXJnaW4iLCAiYmFzYWwgcHJlZ25hbnQiLCAiYmFzYWwgbGFjdGF0ZSIsICJsdW1pbmFsIHZpcmdpbiIsICJsdW1pbmFsIHByZWduYW50IiwgImx1bWluYWwgbGFjdGF0ZSIpCmBgYAoKVGhlbiBsZXQncyBhZGQgdGhpcyBgZ3JvdXBfb3JkZXJgIGludG8gYGJyZWFrcz1gLgoKYGBge3J9CmdncGxvdChkYXRhPXRvcDEwX2NvdW50cywgbWFwcGluZz1hZXMoeD1Hcm91cCwgeT1Ob3JtX2NvdW50cywgY29sPUdyb3VwKSkgKwogIGdlb21faml0dGVyKCkgKwogIGZhY2V0X3dyYXAoflNZTUJPTCwgbnJvdz0yKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpICsKICBzY2FsZV9jb2xvdXJfZGlzY3JldGUoYnJlYWtzPWdyb3VwX29yZGVyKQpgYGAKClRoYXQgcmVvcmRlcmVkIHRoZSBsZWdlbmQgYnV0IG5vdGljZSB0aGF0IGl0IGRpZG4ndCByZW9yZGVyIHRoZSBncm91cHMgb24gdGhlIHggYXhpcy4gSWYgd2Ugd2FudCB0byByZW9yZGVyIGdyb3VwcyBhbG9uZyBhbiBheGlzIHdpdGggZ2dwbG90IHdlIG5lZWQgdG8gbWFrZSB0aGUgY29sdW1uIHdpdGggdGhlIGdyb3VwcyBpbnRvIGFuIFIgZGF0YSB0eXBlIGNhbGxlZCBhIGBmYWN0b3JgLiBGYWN0b3JzIGluIFIgYXJlIHVzZWQgdG8gc3BlY2lmeSBjYXRlZ29yaWVzLgoKV2UnbGwgYWRkIGFub3RoZXIgY29sdW1uIGNhbGxlZCAiR3JvdXBfZiIgYW5kIG1ha2UgdGhlIEdyb3VwIGludG8gYSBmYWN0b3IgYW5kIHNwZWNpZnkgd2hhdCBvcmRlciB3ZSB3YW50IHRoZSBsZXZlbHMgKG5hbWVzIG9mIHRoZSBjYXRlZ29yaWVzKS4KCmBgYHtyfQp0b3AxMF9jb3VudHMgPC0gbXV0YXRlKHRvcDEwX2NvdW50cywgR3JvdXBfZj1mYWN0b3IoR3JvdXAsIGxldmVscz1ncm91cF9vcmRlcikpCmBgYAoKVGFrZSBhIGxvb2sgd2l0aCBgVmlldygpYC4KYGBge3IgZXZhbD1GQUxTRX0KVmlldyh0b3AxMF9jb3VudHMpCmBgYAoKVGhlIEdyb3VwIGFuZCB0aGUgR3JvdXBfZiBjb2x1bW4gbG9vayB0aGUgc2FtZSBidXQgdGFrZSBhIGxvb2sgYnkgdHlwaW5nIGB0b3AxMF9jb3VudHNgLgoKYGBge3J9CnRvcDEwX2NvdW50cwpgYGAKCk5vdGljZSB0aGF0IHRoZSBHcm91cCBjb2x1bW4gaGFzIDxjaHI+IHVuZGVyIHRoZSBoZWFkaW5nLCBpdCBpcyBhIGNoYXJhY3RlciBkYXRhIHR5cGUsIHdoaWxlIHRoZSBHcm91cF9mIGNvbHVtbiBoYXMgPGZjdD4gdW5kZXIgdGhlIGhlYWRpbmcsIGl0IGlzIGEgZmFjdG9yIGRhdGEgdHlwZS4gVGhlIGBzdHIoKWAgY29tbWFuZCBpcyB1c2VmdWwgdG8gY2hlY2sgdGhlIGRhdGEgdHlwZXMgaW4gb2JqZWN0cy4KCmBgYHtyfQpzdHIodG9wMTBfY291bnRzKQpgYGAKCmBzdHIoKWAgc2hvd3MgdXMgR3JvdXBfZiBjb2x1bW4gaXMgYSBGYWN0b3Igd2l0aCA2IGxldmVscy4gVG8gc2VlIHRoZSBsZXZlbHMgd2UgY2FuIHVzZSBgbGV2ZWxzKClgLiBUbyBjaGVjayB0aGUgbGV2ZWxzIG9mIHRoZSBHcm91cF9mIGNvbHVtbnMgd2UgY291bGQgdXNlIGBzZWxlY3QoKWAgdG8gc2VsZWN0IHRoZSBHcm91cF9mIGNvbHVtbiBmb2xsb3dlZCBieSBgcHVsbCgpYCB0byBwdWxsIHRoZSB2YWx1ZXMgb3V0IG9mIGNvbHVtbiBmb3JtYXQuCgpgYGB7cn0KbGV2ZWxzKHB1bGwoc2VsZWN0KHRvcDEwX2NvdW50cywgR3JvdXBfZikpKQpgYGAKCkhvd2V2ZXIsIGluIHRoaXMgY2FzZSBpdCdzIHNpbXBsZXIgdG8gdXNlIHRoZSBiYXNlIFIgbWV0aG9kLCBhcyB3ZSBjYW4gdXNlIGB0b3AxMF9jb3VudHMkR3JvdXBfZmAgdG8gYWNjZXNzIHRoZSB2YWx1ZXMgaW4gdGhlIEdyb3VwX2YgY29sdW1uIGRpcmVjdGx5LgoKYGBge3J9CmxldmVscyh0b3AxMF9jb3VudHMkR3JvdXBfZikKYGBgCgoKV2UgY2FuIHRoZW4gY2hhbmdlIG91ciBwbG90IHRvIHVzZSB0aGUgIkdyb3VwX2YiIGNvbHVtbiBpbnN0ZWFkIG9mIEdyb3VwIChjaGFuZ2UgYHg9YCBhbmQgYGNvbD1gKS4KCmBgYHtyfQpnZ3Bsb3QoZGF0YT10b3AxMF9jb3VudHMsIG1hcHBpbmc9YWVzKHg9R3JvdXBfZiwgeT1Ob3JtX2NvdW50cywgY29sPUdyb3VwX2YpKSArCiAgZ2VvbV9qaXR0ZXIoKSArCiAgZmFjZXRfd3JhcCh+U1lNQk9MLCBucm93PTIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgCmBgYAoKTm93IGJvdGggdGhlIGxlZ2VuZCBhbmQgdGhlIHggYXhpcyBoYXZlIHRoZSBncm91cHMgaW4gdGhlIG9yZGVyIHRoYXQgd2Ugd2FudC4KClRoZXNlIGFyZSB0aGUgdG9wIGdlbmVzIGluIHRoZSBjb21wYXJpc29uIG9mIGx1bWluYWwgY2VsbHMgZnJvbSBwcmVnbmFudCB2cyBsYWN0YXRpbmcgbWljZSBhbmQgdGhpcyB0eXBlIG9mIHBsb3QgZW5hYmxlcyB1cyB0byBzZWUgd2hhdCB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMgbG9vayBsaWtlIGluIGFsbCB0aGUgZ3JvdXBzLiBXZSBjYW4gc2VlIHRoYXQgc29tZSBnZW5lcywgc3VjaCBhcyBQbXZrLCBoYXZlIG1vcmUgc2ltaWxhciBleHByZXNzaW9uIGFjcm9zcyBhbGwgdGhlIGdyb3VwcyB0aGFuIG90aGVycywgc3VjaCBhcyBDc24xczJiLgoKTm90aWNlIHRoYXQgdGhlIGdlbmVzIGhhdmUgYWxzbyBiZWVuIHBsb3R0ZWQgaW4gYWxwaGFiZXRpY2FsIG9yZGVyIGluIHRoZSBmYWNldHMuIElmIHdlIHdhbnRlZCB0byBwbG90IHRoZXNlIGdlbmVzIGluIHRoZSBvcmRlciBvZiBtb3N0IHNpZ25maWNhbnQsIHRoZW4gd2UgbmVlZCB0byBtYWtlIHN5bWJvbCBjb2x1bW4gaW50byBhIGZhY3RvciBhcyB3ZSBkaWQgZm9yIHRoZSBncm91cHMuCgoKIyMjIyBFeGVyY2lzZQoKKiBQbG90IHRoZSBnZW5lcyBpbiBvcmRlciBvZiB0aGUgc2lnbmlmaWNhbmNlLiBIaW50OiBVc2UgbXV0YXRlIHRvIGFkZCBhIGNvbHVtbiBjYWxsZWQgU1lNQk9MX2YgY29udGFpbmluZyBTWU1CT0wgYXMgYSBmYWN0b3Igd2l0aCB0aGUgbGV2ZWxzIGluIHRoZSBvcmRlciBpbiBgdG9wMTBfc3ltc2AuIFRoZW4gcmVta2UgdGhlIHBsb3QgdXNpbmcgU1lNQk9MX2YgaW4gZmFjZXRfd3JhcCBpbnN0ZWFkIG9mIFNZTUJPTC4KCgojIyBDcmVhdGluZyB3b3JrZmxvd3MgdXNpbmcgYCU+JWAKCk9uZSBvZiB0aGUgbW9zdCB1c2VmdWwgYW5kIHBvd2VyZnVsIHRoaW5ncyBhYm91dCB0aGUgdGlkeXZlcnNlIGlzIGRwbHlyJ3MgcGlwZSBvcGVyYXRvciBgJT4lYC4gVGhpcyBlbmFibGVzIHlvdSB0byBjaGFpbiBjb21tbWFuZHMgdG9nZXRoZXIgaW50byB3b3JrZmxvd3MuIEZvciBleGFtcGxlLCB3ZSBjb3VsZCBjaGFpbiB0b2dldGhlciB0aGUgY29tbWFuZHMgd2UgcmFuIGVhcmxpZXIgb24gb3VyIGB0b3AxMF9jb3VudHNgIG9iamVjdC4gTm90ZSB0aGF0IGxpa2UgdGhlIGArYCB3ZSB1c2UgdG8gYWRkIGxheWVycyB0byBhIGdncGxvdCwgYCU+JWAgZ29lcyBhdCB0aGUgZW5kIG9mIHRoZSBsaW5lIGFuZCB0aGlzICdwaXBlcycgdGhlIG91dHB1dCBpbnRvIHRoZSBuZXh0IGNvbW1hbmQuIElmIHdlIHVzZSB0aGUgYCU+JWAgd2UgYWxzbyBkb24ndCBuZWVkIHRvIHNwZWNpZnkgdGhlIGRhdGEgaW5zaWRlIHRoZSBpbmRpdmlkdWFsIGNvbW1hbmRzLCB3ZSBvbmx5IG5lZWQgdG8gc3BlY2lmeSBpdCBhdCB0aGUgYmVnaW5uaW5nIGUuZy4gaW5zdGVhZCBvZiBgZmlsdGVyKG5vcm1fY291bnRzLCBTWU1CT0wgJWluJSB0b3AxMF9zeW1zKWAgd2UgdXNlIGBub3JtX2NvdW50cyAlPiUgZmlsdGVyKFNZTUJPTCAlaW4lIHRvcDEwX3N5bXMpYC4gV2UgY2FuIGFsc28gcGlwZSBvdXRwdXRzIGRpcmVjdGx5IGludG8gYGdncGxvdCgpYC4gVGhlIHBpcGUgaXMgdmVyeSB1c2VmdWwgYnV0IHNvbWUgYWR2aWNlIG9uIHdoZW4gbm90IHRvIHVzZSB0aGUgcGlwZSBpcyBwcm92aWRlZCBieSB0aGUgdGlkeXZlcnNlIGNyZWF0b3IgSGFkbGV5IFdpY2toYW0gaW4gdGhlIGV4Y2VsbGVudCBbUiBmb3IgRGF0YSBTY2llbmNlIGJvb2tdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovcGlwZXMuaHRtbCN3aGVuLW5vdC10by11c2UtdGhlLXBpcGUpLgoKYGBge3J9Cm5vcm1fY291bnRzICU+JQogIGZpbHRlcihTWU1CT0wgJWluJSB0b3AxMF9zeW1zKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZmlsdGVyIHJvd3MgZm9yIHRoZSB0b3AgMTAgZ2VuZXMKICBzZWxlY3QoLUVOVFJFWklELCAtR0VORU5BTUUpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHJlbW92ZSBFTlRSRVpJRCBhbmQgR0VORU5BTUUgY29sdW1ucwogIGdhdGhlcihTYW1wbGUsIE5vcm1fY291bnRzLCAtU1lNQk9MKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICMgY29udmVydCBmcm9tIHdpZGUgdG8gbG9uZyAodGlkeSkgZm9ybWF0CiAgbXV0YXRlKEdyb3VwPWNhc2Vfd2hlbiggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBhZGQgYSBjb2x1bW4gdG8gc3BlY2lmeSBncm91cHMKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkRHIiwgIk1DTDEuREgiKSAgfiAiYmFzYWwgdmlyZ2luIiwKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkRJIiwgIk1DTDEuREoiKSAgfiAiYmFzYWwgcHJlZ25hbnQiLAogICAgICAgIFNhbXBsZSAlaW4lIGMoIk1DTDEuREsiLCAiTUNMMS5ETCIpICB+ICJiYXNhbCBsYWN0YXRlIiwKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkxBIiwgIk1DTDEuTEIiKSAgfiAibHVtaW5hbCB2aXJnaW4iLAogICAgICAgIFNhbXBsZSAlaW4lIGMoIk1DTDEuTEMiLCAiTUNMMS5MRCIpICB+ICJsdW1pbmFsIHByZWduYW50IiwKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkxFIiwgIk1DTDEuTEYiKSAgfiAibHVtaW5hbCBsYWN0YXRlIgogICAgICAgKSkgJT4lCiAgbXV0YXRlKEdyb3VwX2Y9ZmFjdG9yKEdyb3VwLCBsZXZlbHM9Z3JvdXBfb3JkZXIpKSAlPiUgICAgICAgICAgICMgY29udmVydCBncm91cHMgY29sdW1uIHRvIGZhY3RvciBkYXRhIHR5cGUgdG8gc3BlY2lmeSBvcmRlcmluZwogIGdncGxvdChtYXBwaW5nPWFlcyh4PUdyb3VwX2YsIHk9Tm9ybV9jb3VudHMsIGNvbD1Hcm91cF9mKSkgKyAgICAjIG1ha2Ugc3RyaXBjaGFydHMgZmFjZXRlZCBieSBnZW5lCiAgZ2VvbV9qaXR0ZXIoKSArCiAgZmFjZXRfd3JhcCh+U1lNQk9MLCBucm93PTIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgCmBgYApXZSBvbmx5IGhhdmUgdHdvIHBvaW50cyBwZXIgZ3JvdXAgaGVyZSBidXQgaWYgdGhlcmUgd2VyZSBtb3JlIHBvaW50cyB5b3UgY291bGQgb3ZlcmxheSBhbiBlcnJvciBiYXIgd2l0aCBgZ2VvbV9lcnJvcmJhcigpYCBvciBjb21iaW5lIGEgYGdlb21fYm94cGxvdCgpYCB3aXRoIHRoZSBgZ2VvbV9qaXR0ZXIoKWAsIGFzIHNob3duIGZvciB0aGUgc3RyaXBjaGFydHMgaW4gdGhlIHR1dG9yaWFsIFtoZXJlXShodHRwczovL3d3dy5iaW9pbmZvcm1hdGljcy5iYWJyYWhhbS5hYy51ay90cmFpbmluZy9nZ3Bsb3RfY291cnNlL0ludHJvZHVjdGlvbiUyMHRvJTIwZ2dwbG90LnBkZikuCgojIyMjIEV4ZXJjaXNlCgoqIFN0YXJ0aW5nIGZyb20gYG5vcm1fY291bnRzYCBtYWtlIGEgd29ya2Zsb3cgdXNpbmcgYCU+JWAgdGhhdCBjcmVhdGVzIHN0cmlwY2hhcnRzIGZvciB0aGUgZ2VuZXMgVHJwNTMsIEJyY2ExLCBCcmNhMi4KCg==